package com.golf.core.course.loader;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

import com.golf.core.course.Course;
import com.golf.core.course.SimpleCourse;
import com.golf.core.course.bodies.AbstractCourseBody;
import com.golf.core.course.bodies.CourseBody;
import com.golf.core.course.bodies.CourseBoundary;
import com.golf.core.course.bodies.Hole;
import com.golf.core.course.bodies.PolyWall;
import com.golf.core.course.bodies.Position;
import com.golf.core.course.bodies.Wall;
import com.golf.core.course.exceptions.CourseLoadException;

/**
 * 
 * @author gareth
 */
public class CourseLoader
{

	/** The logger instance for this class. */
	private static final Logger log = Logger.getLogger(CourseLoader.class);
	
	
	/** The course instance that will be built. */
    private SimpleCourse course;
    
    
    /** Internal variable: course name */
	private String _name;

    /** Internal variable: course validity flag */
	private boolean _courseValid;
    
    /** Internal variable: course boundary flag */
	private boolean _isBoundary;

    /** Internal variable: current body. */
	private CourseBody _currentBody;

    /** Internal variable: current background. */
	private Background _currentBackground;
	
	
    
    /**
     * Load a named course from a back-end system.
     * 
     * @param courseName The name of the course to load.
     * @return The loaded course.
     * 
     * @throws CourseLoadException If there is a problem loading the requested course.
     */
    public static Course load(String courseName) throws CourseLoadException
    {
        Reader in = null;
        try
        {
            final InputStream i = CourseLoader.class.getResourceAsStream(courseName);
            in = new InputStreamReader(i, "UTF-8");
            
            final CourseLoader l = new CourseLoader();
            return l.parse(in);
        }
        catch (IOException ie)
        {
            throw new CourseLoadException("unable to parse course data ...", ie);
        }
        finally
        {
        	if (in != null)
        	{
        		IOUtils.closeQuietly(in);
        	}
        }
    }
    
    
    /**
     * 
     * 
     * @param in
     * @return
     * 
     * @throws CourseLoadException
     */
    private Course parse(final Reader in) throws CourseLoadException
    {
        XMLStreamReader parser = null;
        try
        {
            XMLInputFactory factory = XMLInputFactory.newInstance();
            parser = factory.createXMLStreamReader(in);

            for (int event = parser.next() ; 
                 event != XMLStreamConstants.END_DOCUMENT ;
                 event = parser.next())
            {
                switch (event)
                {
                    case XMLStreamConstants.START_ELEMENT:
                    {
                        parseElement(parser.getName().getLocalPart(), parser);
                        break;
                    }
                    case XMLStreamConstants.END_ELEMENT:
                    {
                        parseCloseElement(parser.getName().getLocalPart(), parser);
                        break;
                    }
                    case XMLStreamConstants.CHARACTERS:
                    {
                        break;
                    }
                    case XMLStreamConstants.CDATA:
                    {
                        break;
                    }
                    default:
                    {
                    	// Unexpected STAX event type, do nothing.
                    	break;
                    }
                } 
            } 
        }
        catch (XMLStreamException ex)
        {
            System.out.println(ex);
        }
        finally
        {
            if (parser != null)
            {
                try
                {
                    parser.close();
                } 
                catch (XMLStreamException e)
                {
                    // Do nthoing. for now
                }
            }
        }

        return course;
    }


	/**
	 * 
	 * 
	 * @param name
	 * @param parser
	 * 
	 * @throws XMLStreamException
	 */
	private void parseElement(final String name, final XMLStreamReader parser) 
	throws XMLStreamException
    {
        log.debug("parsing element: " + name);
        
        // These are elements that can be read at all times.
        if ("Course".equals(name))
        {
        	_name = parser.getAttributeValue(null, "name");
        }
        else if ("StartPosition".equals(name))
        {
        	final Position startPos = new Position(Float.parseFloat(parser.getAttributeValue(null, "x")),
        										   Float.parseFloat(parser.getAttributeValue(null, "x")));
        	course = new SimpleCourse(_name, startPos);
        	_courseValid = true;
        }
        else if (_courseValid)
        {
        	// Any other elements require a valid course to have been parsed.
	        if ("Boundary".equals(name))
	        {
	        	_isBoundary = true;
	        }
	        else if ("Hole".equals(name))
	        {
	            _currentBody = new Hole("hole", new Position(                          
	                    Float.parseFloat(parser.getAttributeValue(null, "x")),
                        Float.parseFloat(parser.getAttributeValue(null, "y")) ) );
	        }
	        else if ("Wall".equals(name))
	        {
	        	_currentBody = new Wall(  parser.getAttributeValue(null, "name"),
	                                Float.parseFloat(parser.getAttributeValue(null, "x")),
	                                Float.parseFloat(parser.getAttributeValue(null, "y")),
	                                Float.parseFloat(parser.getAttributeValue(null, "width")),
	                                Float.parseFloat(parser.getAttributeValue(null, "height")) );
	        }
	        else if ("PolyWall".equals(name))
	        {
	        	_currentBody = new PolyWall(parser.getAttributeValue(null, "name"));
	        }
	        else if ("Point".equals(name))
	        {
	        	if (_currentBody != null)
	        	{
	        		PolyWall wall = (PolyWall)_currentBody;
	        		wall.addVertex(new Position(Float.parseFloat(parser.getAttributeValue(null, "x")),
	        		 						    Float.parseFloat(parser.getAttributeValue(null, "y"))));
	        	}
	        }
	        else if ("Background".equals(name))
	        {
	        	if ("colour".equals(parser.getAttributeValue(null, "type")))
	        	{
 	        		String color = parser.getElementText();
	        		_currentBackground = new Background(color);
	                if (_currentBody != null)
	                {
	                    // If a background has been parsed, and inside a body, add this to the body.
	                    ((AbstractCourseBody)_currentBody).setBackground(_currentBackground);
	                }
	        	}
	        	else if ("rgba".equals(parser.getAttributeValue(null, "type")))
                {
                    String rgba = parser.getElementText();
                    String[] bits = rgba.split(",");
                    
                    _currentBackground = new Background(Integer.parseInt(bits[0]), Integer.parseInt(bits[1]), Integer.parseInt(bits[2]), Integer.parseInt(bits[3]));
                    if (_currentBody != null)
                    {
                        // If a background has been parsed, and inside a body, add this to the body.
                        ((AbstractCourseBody)_currentBody).setBackground(_currentBackground);
                    }
                }
	        }
        }
        else
        {
            // error
            
        }
    }


    /**
     * 
     * 
     * @param localPart
     * @param parser
     */
    private void parseCloseElement(final String localPart, final XMLStreamReader parser) 
    {
    	if ("PolyWall".equals(localPart) || "Wall".equals(localPart) || "Hole".equals(localPart))
    	{
    		if (!_isBoundary)
    		{
	            course.getBodies().add(_currentBody);
	            _currentBody = null;
    		}
    	}
    	else if ("Boundary".equals(localPart))
    	{
            CourseBoundary boundary = new CourseBoundary(_currentBody);
            course.setBoundary(boundary);
            
            _currentBody =  null;
            _isBoundary = false;
    	}
    }
	
}
